# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt

import frappe
from frappe import _
from frappe.query_builder.functions import Sum
from frappe.utils import add_days, cstr, flt, formatdate, getdate, date_diff, add_months, get_last_day, add_years

import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
    get_accounting_dimensions,
    get_dimension_with_children,
)
from erpnext.accounts.report.financial_statements import (
    filter_accounts,
    filter_out_zero_value_rows,
    set_gl_entries_by_account,
)
from erpnext.accounts.report.utils import convert_to_presentation_currency, get_currency
from erpnext.accounts.utils import get_zero_cutoff

value_fields = (
    "opening_debit",
    "opening_credit",
    "debit",
    "credit",
    "closing_debit",
    "closing_credit",
)


def execute(filters=None):
    validate_filters(filters)
    data = get_data(filters)
    columns = get_columns(filters)
    return columns, data


def validate_filters(filters):
    if not filters.fiscal_year:
        frappe.throw(_("Fiscal Year {0} is required").format(filters.fiscal_year))

    fiscal_year = frappe.get_cached_value(
        "Fiscal Year", filters.fiscal_year, ["year_start_date", "year_end_date"], as_dict=True
    )
    if not fiscal_year:
        frappe.throw(_("Fiscal Year {0} does not exist").format(filters.fiscal_year))
    else:
        filters.year_start_date = getdate(fiscal_year.year_start_date)
        filters.year_end_date = getdate(fiscal_year.year_end_date)

    if not filters.from_date:
        filters.from_date = filters.year_start_date

    if not filters.to_date:
        filters.to_date = filters.year_end_date

    filters.from_date = getdate(filters.from_date)
    filters.to_date = getdate(filters.to_date)

    if filters.from_date > filters.to_date:
        frappe.throw(_("From Date cannot be greater than To Date"))

    if (filters.from_date < filters.year_start_date) or (filters.from_date > filters.year_end_date):
        frappe.msgprint(
            _("From Date should be within the Fiscal Year. Assuming From Date = {0}").format(
                formatdate(filters.year_start_date)
            )
        )
        filters.from_date = filters.year_start_date

    if (filters.to_date < filters.year_start_date) or (filters.to_date > filters.year_end_date):
        frappe.msgprint(
            _("To Date should be within the Fiscal Year. Assuming To Date = {0}").format(
                formatdate(filters.year_end_date)
            )
        )
        filters.to_date = filters.year_end_date

    if filters.show_monthly_trends:
        if not filters.get("periodicity"):
            filters.periodicity = "Monthly"
        if not filters.get("axis"):
            filters.axis = "Horizontal"


def get_period_ranges(filters):
    """Generate date ranges based on selected periodicity"""
    ranges = []
    current_start = filters.from_date
    periodicity = filters.periodicity or "Monthly"

    while current_start <= filters.to_date:
        if periodicity == "Monthly":
            period_end = get_last_day(current_start)
            next_start = add_months(current_start, 1)
            label = current_start.strftime("%b %Y")
            key = current_start.strftime("%Y-%m")
        
        elif periodicity == "Quarterly":
            period_end = get_last_day(add_months(current_start, 2))
            next_start = add_months(current_start, 3)
            quarter = (current_start.month - 1) // 3 + 1
            label = f"Q{quarter} {current_start.year}"
            key = f"{current_start.year}-Q{quarter}"
        
        elif periodicity == "Half-Yearly":
            period_end = get_last_day(add_months(current_start, 5))
            next_start = add_months(current_start, 6)
            half = 1 if current_start.month <= 6 else 2
            label = f"H{half} {current_start.year}"
            key = f"{current_start.year}-H{half}"
        
        elif periodicity == "Yearly":
            period_end = get_last_day(add_months(current_start, 11))
            next_start = add_years(current_start, 1)
            label = f"{current_start.year}"
            key = f"{current_start.year}"

        period_end = min(period_end, filters.to_date)
        ranges.append({
            "start": current_start,
            "end": period_end,
            "label": label,
            "key": key
        })
        
        current_start = next_start

    return ranges


def get_data(filters):
    accounts = frappe.db.sql(
        """select name, account_number, parent_account, account_name, root_type, report_type, is_group, lft, rgt
        from `tabAccount` where company=%s order by lft""",
        filters.company,
        as_dict=True,
    )
    company_currency = filters.presentation_currency or erpnext.get_company_currency(filters.company)

    ignore_is_opening = frappe.get_single_value("Accounts Settings", "ignore_is_opening_check_for_reporting")

    if not accounts:
        return None

    accounts, accounts_by_name, parent_children_map = filter_accounts(accounts)

    gl_entries_by_account = {}

    opening_balances = get_opening_balances(filters, ignore_is_opening)

    if filters.project:
        filters.project = [filters.project]

    set_gl_entries_by_account(
        filters.company,
        filters.from_date,
        filters.to_date,
        filters,
        gl_entries_by_account,
        root_lft=None,
        root_rgt=None,
        ignore_closing_entries=not flt(filters.with_period_closing_entry_for_current_period),
        ignore_opening_entries=True,
        group_by_account=True,
    )

    periodic_data = {}
    if filters.get("show_monthly_trends"):
        periodic_data = get_periodic_trends_data(filters, accounts, ignore_is_opening)

    calculate_values(
        accounts,
        gl_entries_by_account,
        opening_balances,
        filters.get("show_net_values"),
        ignore_is_opening=ignore_is_opening,
    )
    accumulate_values_into_parents(accounts, accounts_by_name)

    data = prepare_data(accounts, filters, parent_children_map, company_currency, periodic_data)
    data = filter_out_zero_value_rows(
        data, parent_children_map, show_zero_values=filters.get("show_zero_values")
    )

    return data


def get_periodic_trends_data(filters, accounts, ignore_is_opening):
    periodic_data = {}
    account_list = [d.name for d in accounts]
    periods = get_period_ranges(filters)
    
    for account in account_list:
        periodic_data[account] = {}

    for period in periods:
        gl_entries = get_periodic_gl_entries(
            filters, account_list, period["start"], period["end"], ignore_is_opening
        )
        
        for entry in gl_entries:
            if entry.account not in periodic_data:
                continue
                
            if period["key"] not in periodic_data[entry.account]:
                periodic_data[entry.account][period["key"]] = {
                    "debit": 0.0,
                    "credit": 0.0,
                    "label": period["label"]
                }
                
            periodic_data[entry.account][period["key"]]["debit"] += flt(entry.debit)
            periodic_data[entry.account][period["key"]]["credit"] += flt(entry.credit)
    
    return periodic_data


def get_periodic_gl_entries(filters, accounts, start_date, end_date, ignore_is_opening):
    gle = frappe.qb.DocType("GL Entry")
    
    query = (
        frappe.qb.from_(gle)
        .select(
            gle.account,
            Sum(gle.debit).as_("debit"),
            Sum(gle.credit).as_("credit")
        )
        .where(
            (gle.company == filters.company) &
            (gle.account.isin(accounts)) &
            (gle.posting_date >= start_date) &
            (gle.posting_date <= end_date) &
            (gle.is_cancelled == 0)
        )
        .groupby(gle.account)
    )
    
    if not ignore_is_opening:
        query = query.where(gle.is_opening == "No")
        
    if not flt(filters.with_period_closing_entry_for_current_period):
        query = query.where(gle.voucher_type != "Period Closing Voucher")
    
    if filters.cost_center:
        lft, rgt = frappe.db.get_value("Cost Center", filters.cost_center, ["lft", "rgt"])
        cost_center = frappe.qb.DocType("Cost Center")
        query = query.where(
            gle.cost_center.isin(
                frappe.qb.from_(cost_center)
                .select("name")
                .where((cost_center.lft >= lft) & (cost_center.rgt <= rgt))
            )
        )
    
    if filters.project:
        query = query.where(gle.project == filters.project)
    
    return query.run(as_dict=1)


def get_opening_balances(filters, ignore_is_opening, exchange_rate=None, ignore_reporting_currency=True):
    balance_sheet_opening = get_rootwise_opening_balances(
        filters, "Balance Sheet", ignore_is_opening, exchange_rate, ignore_reporting_currency
    )
    pl_opening = get_rootwise_opening_balances(
        filters, "Profit and Loss", ignore_is_opening, exchange_rate, ignore_reporting_currency
    )

    balance_sheet_opening.update(pl_opening)
    return balance_sheet_opening


def get_rootwise_opening_balances(
    filters, report_type, ignore_is_opening, exchange_rate=None, ignore_reporting_currency=True
):
    gle = []
    last_period_closing_voucher = ""
    ignore_closing_balances = frappe.get_single_value("Accounts Settings", "ignore_account_closing_balance")

    if not ignore_closing_balances:
        last_period_closing_voucher = frappe.db.get_all(
            "Period Closing Voucher",
            filters={"docstatus": 1, "company": filters.company, "period_end_date": ("<", filters.from_date)},
            fields=["period_end_date", "name"],
            order_by="period_end_date desc",
            limit=1,
        )

    accounting_dimensions = get_accounting_dimensions(as_list=False)

    if last_period_closing_voucher:
        gle = get_opening_balance(
            "Account Closing Balance",
            filters,
            report_type,
            accounting_dimensions,
            period_closing_voucher=last_period_closing_voucher[0].name,
            ignore_is_opening=ignore_is_opening,
            ignore_reporting_currency=ignore_reporting_currency,
        )

        if getdate(last_period_closing_voucher[0].period_end_date) < getdate(add_days(filters.from_date, -1)):
            start_date = add_days(last_period_closing_voucher[0].period_end_date, 1)
            gle += get_opening_balance(
                "GL Entry",
                filters,
                report_type,
                accounting_dimensions,
                start_date=start_date,
                ignore_is_opening=ignore_is_opening,
                ignore_reporting_currency=ignore_reporting_currency,
            )
    else:
        gle = get_opening_balance(
            "GL Entry",
            filters,
            report_type,
            accounting_dimensions,
            ignore_is_opening=ignore_is_opening,
            ignore_reporting_currency=ignore_reporting_currency,
        )

    opening = frappe._dict()
    for d in gle:
        opening_dr_cr = {
            "account": d.account,
            "opening_debit": 0.0,
            "opening_credit": 0.0,
        }

        opening.setdefault(d.account, opening_dr_cr)

        if ignore_reporting_currency:
            opening[d.account]["opening_debit"] += flt(d.debit)
            opening[d.account]["opening_credit"] += flt(d.credit)
        else:
            if d.get("report_type") == "Balance Sheet" and not (
                d.get("root_type") == "Equity" or d.get("account_type") == "Equity"
            ):
                opening[d.account]["opening_debit"] += flt(d.debit) * flt(exchange_rate)
                opening[d.account]["opening_credit"] += flt(d.credit) * flt(exchange_rate)
            else:
                opening[d.account]["opening_debit"] += flt(d.debit_in_reporting_currency)
                opening[d.account]["opening_credit"] += flt(d.credit_in_reporting_currency)

    return opening


def get_opening_balance(
    doctype,
    filters,
    report_type,
    accounting_dimensions,
    period_closing_voucher=None,
    start_date=None,
    ignore_is_opening=0,
    ignore_reporting_currency=True,
):
    closing_balance = frappe.qb.DocType(doctype)
    accounts = frappe.db.get_all("Account", filters={"report_type": report_type}, pluck="name")

    opening_balance = (
        frappe.qb.from_(closing_balance)
        .select(
            closing_balance.account,
            closing_balance.account_currency,
            Sum(closing_balance.debit).as_("debit"),
            Sum(closing_balance.credit).as_("credit"),
            Sum(closing_balance.debit_in_account_currency).as_("debit_in_account_currency"),
            Sum(closing_balance.credit_in_account_currency).as_("credit_in_account_currency"),
        )
        .where((closing_balance.company == filters.company) & (closing_balance.account.isin(accounts)))
        .groupby(closing_balance.account)
    )

    if not ignore_reporting_currency:
        opening_balance = opening_balance.select(
            Sum(closing_balance.debit_in_reporting_currency).as_("debit_in_reporting_currency"),
            Sum(closing_balance.credit_in_reporting_currency).as_("credit_in_reporting_currency"),
        )

    if period_closing_voucher:
        opening_balance = opening_balance.where(
            closing_balance.period_closing_voucher == period_closing_voucher
        )
    else:
        if start_date:
            opening_balance = opening_balance.where(
                (closing_balance.posting_date >= start_date)
                & (closing_balance.posting_date < filters.from_date)
            )

            if not ignore_is_opening:
                opening_balance = opening_balance.where(closing_balance.is_opening == "No")
        else:
            if not ignore_is_opening:
                opening_balance = opening_balance.where(
                    (closing_balance.posting_date < filters.from_date) | (closing_balance.is_opening == "Yes")
                )
            else:
                opening_balance = opening_balance.where(closing_balance.posting_date < filters.from_date)

    if doctype == "GL Entry":
        opening_balance = opening_balance.where(closing_balance.is_cancelled == 0)

    if (
        not filters.show_unclosed_fy_pl_balances
        and report_type == "Profit and Loss"
        and doctype == "GL Entry"
    ):
        opening_balance = opening_balance.where(closing_balance.posting_date >= filters.year_start_date)

    if not flt(filters.with_period_closing_entry_for_opening):
        if doctype == "Account Closing Balance":
            opening_balance = opening_balance.where(closing_balance.is_period_closing_voucher_entry == 0)
        else:
            opening_balance = opening_balance.where(closing_balance.voucher_type != "Period Closing Voucher")

    if filters.cost_center:
        lft, rgt = frappe.db.get_value("Cost Center", filters.cost_center, ["lft", "rgt"])
        cost_center = frappe.qb.DocType("Cost Center")
        opening_balance = opening_balance.where(
            closing_balance.cost_center.isin(
                frappe.qb.from_(cost_center)
                .select("name")
                .where((cost_center.lft >= lft) & (cost_center.rgt <= rgt))
            )
        )

    if filters.project:
        opening_balance = opening_balance.where(closing_balance.project == filters.project)

    if frappe.db.count("Finance Book"):
        if filters.get("include_default_book_entries"):
            company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")

            if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
                frappe.throw(
                    _("To use a different finance book, please uncheck 'Include Default FB Entries'")
                )

            opening_balance = opening_balance.where(
                (closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
                | (closing_balance.finance_book.isnull())
            )
        else:
            opening_balance = opening_balance.where(
                (closing_balance.finance_book.isin([cstr(filters.finance_book), ""]))
                | (closing_balance.finance_book.isnull())
            )

    if accounting_dimensions:
        for dimension in accounting_dimensions:
            if filters.get(dimension.fieldname):
                if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
                    filters[dimension.fieldname] = get_dimension_with_children(
                        dimension.document_type, filters.get(dimension.fieldname)
                    )
                    opening_balance = opening_balance.where(
                        closing_balance[dimension.fieldname].isin(filters[dimension.fieldname])
                    )
                else:
                    opening_balance = opening_balance.where(
                        closing_balance[dimension.fieldname].isin(filters[dimension.fieldname])
                    )

    gle = opening_balance.run(as_dict=1)

    if filters and filters.get("presentation_currency") and ignore_reporting_currency:
        convert_to_presentation_currency(gle, get_currency(filters))

    return gle


def calculate_values(
    accounts,
    gl_entries_by_account,
    opening_balances,
    show_net_values,
    ignore_is_opening=0,
    exchange_rate=None,
    ignore_reporting_currency=True,
):
    init = {
        "opening_debit": 0.0,
        "opening_credit": 0.0,
        "debit": 0.0,
        "credit": 0.0,
        "closing_debit": 0.0,
        "closing_credit": 0.0,
    }

    for d in accounts:
        d.update(init.copy())

        d["opening_debit"] = opening_balances.get(d.name, {}).get("opening_debit", 0)
        d["opening_credit"] = opening_balances.get(d.name, {}).get("opening_credit", 0)

        for entry in gl_entries_by_account.get(d.name, []):
            if cstr(entry.is_opening) != "Yes" or ignore_is_opening:
                if ignore_reporting_currency:
                    d["debit"] += flt(entry.debit)
                    d["credit"] += flt(entry.credit)
                else:
                    if d.report_type == "Balance Sheet" and not (
                        d.root_type == "Equity" or d.account_type == "Equity"
                    ):
                        d["debit"] += flt(entry.debit) * flt(exchange_rate)
                        d["credit"] += flt(entry.credit) * flt(exchange_rate)
                    else:
                        d["debit"] += flt(entry.debit_in_reporting_currency)
                        d["credit"] += flt(entry.credit_in_reporting_currency)

        d["closing_debit"] = d["opening_debit"] + d["debit"]
        d["closing_credit"] = d["opening_credit"] + d["credit"]

        if show_net_values:
            prepare_opening_closing(d)


def accumulate_values_into_parents(accounts, accounts_by_name):
    for d in reversed(accounts):
        if d.parent_account:
            for key in value_fields:
                accounts_by_name[d.parent_account][key] += d[key]
            
            for key in list(d.keys()):
                if (key.endswith("_debit") or key.endswith("_credit")) and not key.startswith(("opening_", "closing_")):
                    accounts_by_name[d.parent_account][key] = accounts_by_name[d.parent_account].get(key, 0.0) + d[key]


def prepare_data(accounts, filters, parent_children_map, company_currency, periodic_data):
    data = []
    periodic_columns = []
    
    if filters.get("show_monthly_trends") and periodic_data:
        all_periods = set()
        for account_data in periodic_data.values():
            all_periods.update(account_data.keys())
        sorted_periods = sorted(all_periods)
        
        for period in sorted_periods:
            period_label = None
            for account_data in periodic_data.values():
                if period in account_data:
                    period_label = account_data[period]["label"]
                    break
            periodic_columns.append({
                "fieldname": period,
                "label": period_label or period
            })

    for d in accounts:
        if parent_children_map.get(d.account) and filters.get("show_net_values"):
            prepare_opening_closing(d)

        if filters.get("show_monthly_trends") and filters.get("axis") == "Vertical":
            debit_row = create_vertical_mode_row(d, "Debit", filters, periodic_data, company_currency)
            credit_row = create_vertical_mode_row(d, "Credit", filters, periodic_data, company_currency)
            
            if debit_row["has_value"]:
                data.append(debit_row)
            if credit_row["has_value"]:
                data.append(credit_row)
        else:
            row = create_horizontal_mode_row(d, filters, periodic_data, company_currency)
            data.append(row)

    total_rows = calculate_vertical_total_rows(accounts, company_currency, filters, periodic_columns) if \
                 (filters.get("show_monthly_trends") and filters.get("axis") == "Vertical") else \
                 [calculate_horizontal_total_row(accounts, company_currency, filters, periodic_columns)]

    if not filters.get("show_group_accounts"):
        data = hide_group_accounts(data)

    data.extend(total_rows)

    return data


def create_vertical_mode_row(account, direction, filters, periodic_data, company_currency):
    row = {
        "account": account.name,
        "account_name": f"{account.account_name} ({direction})",
        "direction": direction,
        "parent_account": account.parent_account,
        "indent": account.indent + 1,
        "currency": company_currency,
        "is_group_account": account.is_group,
        "has_value": False
    }

    opening_field = "opening_debit" if direction == "Debit" else "opening_credit"
    row["opening"] = flt(account.get(opening_field, 0.0), 3)
    if abs(row["opening"]) > 0:
        row["has_value"] = True

    if periodic_data.get(account.name):
        for period in sorted(periodic_data[account.name].keys()):
            period_value = periodic_data[account.name][period].get(direction.lower(), 0.0)
            row[period] = flt(period_value, 3)
            if abs(row[period]) > 0:
                row["has_value"] = True

    total_field = "debit" if direction == "Debit" else "credit"
    closing_field = "closing_debit" if direction == "Debit" else "closing_credit"
    
    row["total"] = flt(account.get(total_field, 0.0), 3)
    row["closing"] = flt(account.get(closing_field, 0.0), 3)
    
    if abs(row["total"]) > 0 or abs(row["closing"]) > 0:
        row["has_value"] = True

    return row


def create_horizontal_mode_row(account, filters, periodic_data, company_currency):
    row = {
        "account": account.name,
        "parent_account": account.parent_account,
        "indent": account.indent,
        "currency": company_currency,
        "is_group_account": account.is_group,
        "account_name": f"{account.account_number} - {account.account_name}" if account.account_number else account.account_name,
        "has_value": False
    }

    for key in value_fields:
        row[key] = flt(account.get(key, 0.0), 3)
        if abs(row[key]) > 0:
            row["has_value"] = True

    if filters.get("show_monthly_trends") and periodic_data.get(account.name):
        for period in sorted(periodic_data[account.name].keys()):
            row[f"{period}_debit"] = flt(periodic_data[account.name][period]["debit"], 3)
            row[f"{period}_credit"] = flt(periodic_data[account.name][period]["credit"], 3)
            if abs(row[f"{period}_debit"]) > 0 or abs(row[f"{period}_credit"]) > 0:
                row["has_value"] = True

    return row


def get_columns(filters):
    columns = [
        {
            "fieldname": "account",
            "label": _("Account"),
            "fieldtype": "Link",
            "options": "Account",
            "width": 300,
        },
        {
            "fieldname": "direction",
            "label": _("Direction"),
            "fieldtype": "Data",
            "width": 100,
            "hidden": not (filters.get("show_monthly_trends") and filters.get("axis") == "Vertical")
        }
    ]

    columns.extend([
        {
            "fieldname": "opening",
            "label": _("Opening"),
            "fieldtype": "Currency",
            "options": "currency",
            "width": 120,
            "hidden": not (filters.get("show_monthly_trends") and filters.get("axis") == "Vertical")
        },
        {
            "fieldname": "opening_debit",
            "label": _("Opening (Dr)"),
            "fieldtype": "Currency",
            "options": "currency",
            "width": 120,
            "hidden": filters.get("show_monthly_trends") and filters.get("axis") == "Vertical"
        },
        {
            "fieldname": "opening_credit",
            "label": _("Opening (Cr)"),
            "fieldtype": "Currency",
            "options": "currency",
            "width": 120,
            "hidden": filters.get("show_monthly_trends") and filters.get("axis") == "Vertical"
        }
    ])

    if filters.get("show_monthly_trends"):
        periods = get_period_ranges(filters)
        axis = filters.get("axis", "Horizontal")
        
        if axis == "Vertical":
            for period in periods:
                columns.append({
                    "fieldname": period["key"],
                    "label": period["label"],
                    "fieldtype": "Currency",
                    "options": "currency",
                    "width": 120,
                })
        else:
            for period in periods:
                columns.append({
                    "fieldname": f"{period['key']}_debit",
                    "label": f"{period['label']} (Dr)",
                    "fieldtype": "Currency",
                    "options": "currency",
                    "width": 120,
                })
                columns.append({
                    "fieldname": f"{period['key']}_credit",
                    "label": f"{period['label']} (Cr)",
                    "fieldtype": "Currency",
                    "options": "currency",
                    "width": 120,
                })

    total_columns = [
        {
            "fieldname": "total",
            "label": _("Total"),
            "fieldtype": "Currency",
            "options": "currency",
            "width": 120,
            "hidden": not (filters.get("show_monthly_trends") and filters.get("axis") == "Vertical")
        },
        {
            "fieldname": "debit",
            "label": _("Total Debit"),
            "fieldtype": "Currency",
            "options": "currency",
            "width": 120,
            "hidden": filters.get("show_monthly_trends") and filters.get("axis") == "Vertical"
        },
        {
            "fieldname": "credit",
            "label": _("Total Credit"),
            "fieldtype": "Currency",
            "options": "currency",
            "width": 120,
            "hidden": filters.get("show_monthly_trends") and filters.get("axis") == "Vertical"
        },
        {
            "fieldname": "closing",
            "label": _("Closing"),
            "fieldtype": "Currency",
            "options": "currency",
            "width": 120,
            "hidden": not (filters.get("show_monthly_trends") and filters.get("axis") == "Vertical")
        },
        {
            "fieldname": "closing_debit",
            "label": _("Closing (Dr)"),
            "fieldtype": "Currency",
            "options": "currency",
            "width": 120,
            "hidden": filters.get("show_monthly_trends") and filters.get("axis") == "Vertical"
        },
        {
            "fieldname": "closing_credit",
            "label": _("Closing (Cr)"),
            "fieldtype": "Currency",
            "options": "currency",
            "width": 120,
            "hidden": filters.get("show_monthly_trends") and filters.get("axis") == "Vertical"
        }
    ]
    
    columns.extend(total_columns)
    return columns


def calculate_horizontal_total_row(accounts, company_currency, filters, periodic_columns):
    total_row = {
        "account": "'" + _("Total") + "'",
        "account_name": "'" + _("Total") + "'",
        "warn_if_negative": True,
        "opening_debit": 0.0,
        "opening_credit": 0.0,
        "debit": 0.0,
        "credit": 0.0,
        "closing_debit": 0.0,
        "closing_credit": 0.0,
        "parent_account": None,
        "indent": 0,
        "has_value": True,
        "currency": company_currency,
    }

    if filters.get("show_monthly_trends"):
        axis = filters.get("axis", "Horizontal")
        for col in periodic_columns:
            if axis == "Horizontal":
                total_row[f"{col['fieldname']}_debit"] = 0.0
                total_row[f"{col['fieldname']}_credit"] = 0.0
            else:
                for type_ in ["opening_debit", "opening_credit", "debit", "credit", "closing_debit", "closing_credit"]:
                    total_row[f"{col['fieldname']}_{type_}"] = 0.0

    for d in accounts:
        if not d.parent_account:
            for field in value_fields:
                total_row[field] += d[field]
            
            if filters.get("show_monthly_trends"):
                axis = filters.get("axis", "Horizontal")
                for col in periodic_columns:
                    if axis == "Horizontal":
                        month_key = col["fieldname"]
                        total_row[f"{month_key}_debit"] += d.get(f"{month_key}_debit", 0.0)
                        total_row[f"{month_key}_credit"] += d.get(f"{month_key}_credit", 0.0)
                    else:
                        period_key = col["fieldname"]
                        for type_ in ["opening_debit", "opening_credit", "debit", "credit", "closing_debit", "closing_credit"]:
                            total_row[f"{period_key}_{type_}"] += d.get(f"{period_key}_{type_}", 0.0)

    return total_row


def calculate_vertical_total_rows(accounts, company_currency, filters, periodic_columns):
    debit_total = {
        "account": "'" + _("Total Debit") + "'",
        "account_name": "'" + _("Total Debit") + "'",
        "direction": "Debit",
        "parent_account": None,
        "indent": 0,
        "has_value": True,
        "currency": company_currency,
        "opening": 0.0,
        "total": 0.0,
        "closing": 0.0
    }
    
    credit_total = {
        "account": "'" + _("Total Credit") + "'",
        "account_name": "'" + _("Total Credit") + "'",
        "direction": "Credit",
        "parent_account": None,
        "indent": 0,
        "has_value": True,
        "currency": company_currency,
        "opening": 0.0,
        "total": 0.0,
        "closing": 0.0
    }

    for col in periodic_columns:
        debit_total[col["fieldname"]] = 0.0
        credit_total[col["fieldname"]] = 0.0

    for d in accounts:
        if not d.parent_account:
            debit_total["opening"] += d.get("opening_debit", 0.0)
            credit_total["opening"] += d.get("opening_credit", 0.0)
            
            debit_total["total"] += d.get("debit", 0.0)
            credit_total["total"] += d.get("credit", 0.0)
            
            debit_total["closing"] += d.get("closing_debit", 0.0)
            credit_total["closing"] += d.get("closing_credit", 0.0)
            
            for col in periodic_columns:
                period_key = col["fieldname"]
                debit_total[period_key] += d.get(f"{period_key}_debit", 0.0)
                credit_total[period_key] += d.get(f"{period_key}_credit", 0.0)

    return [debit_total, credit_total]


def prepare_opening_closing(row):
    dr_or_cr = "debit" if row["root_type"] in ["Asset", "Equity", "Expense"] else "credit"
    reverse_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"

    for col_type in ["opening", "closing"]:
        valid_col = col_type + "_" + dr_or_cr
        reverse_col = col_type + "_" + reverse_dr_or_cr
        row[valid_col] -= row[reverse_col]
        if row[valid_col] < 0:
            row[reverse_col] = abs(row[valid_col])
            row[valid_col] = 0.0
        else:
            row[reverse_col] = 0.0


def hide_group_accounts(data):
    non_group_accounts_data = []
    for d in data:
        if not d.get("is_group_account"):
            d.update(indent=0)
            non_group_accounts_data.append(d)
    return non_group_accounts_data